package com.sinotn.ks.node.rtlog;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;

import com.sinotn.SinotnLogger;
import com.sinotn.ks.definition.ExamLogger;
import com.sinotn.ks.definition.RuntimeLog;
import com.sinotn.util.StreamUtil;

/**
 * 行为日志分区存储实现
 * @Title LogRegion.java
 * @Package com.sinotn.ks.node.rtlog
 * @Description 
 * Copyright: Copyright (c) 2025
 * Company:广东峰上科技有限公司
 * 
 * @author <a href="mailto:xieqj@fstec.com">谢启进</a>
 * @date 2025年8月28日 下午4:31:06
 * @version V1.0
 */
public class LogRegion {
	public static final String META_FILE="write.m";
	public static final String DATA_FILE="data.dt";
	private static final boolean trace=SinotnLogger.DEBUG.isTraceEnabled();
	private final Charset charset=Charset.forName("UTF-8");
	private ReentrantLock lock=new ReentrantLock();
	// 头文件20字节，
	//8字节[0-7]记录写入位置;
	//8字节[8-15]记录总记录数;
	public static final int HEAD_SIZE=16;
	private File folder;
	private RandomAccessFile headRaf;
	private FileChannel headChannel;
	private MappedByteBuffer headMappedByteBuffer;
	// 总写入偏移位置
	private AtomicLong writePos;
	// 总写入记录数
	private AtomicLong rowCount;
	
	private RandomAccessFile dataRaf;
	private FileChannel dataChannel;
	private ByteBuffer writeBuffer;
	private String id;
	
	public LogRegion(File regionFolder) {
		this.folder=regionFolder;
	}
	
	public String getId() {
		return id;
	}

	public void closeResource(){
		if(this.dataChannel!=null){
			try{
				this.dataChannel.close();
			}catch(Throwable e1){}
			this.dataChannel=null;
		}
		if(this.dataRaf!=null){
			try{
				this.dataRaf.close();
			}catch(Throwable e1){}
			this.dataRaf=null;
		}
		if(this.headMappedByteBuffer!=null){
			ByteBufferUtil.clean(this.headMappedByteBuffer);
			this.headMappedByteBuffer=null;
		}
		if(this.headChannel!=null){
			try{
				this.headChannel.close();
			}catch(Throwable e1){}
			this.headChannel=null;
		}
		if(this.headRaf!=null){
			try{
				this.headRaf.close();
			}catch(Throwable e1){}
			this.headRaf=null;
		}
	}
	
	public void init(){
		// 写消息初始化
		File head=new File(this.folder, META_FILE);
		boolean ok=false;
		try{
			File dtfile=new File(this.folder, DATA_FILE);
			this.id=this.folder.getName();
			this.dataRaf=new RandomAccessFile(dtfile, "rw");
			this.dataChannel=this.dataRaf.getChannel();
			
			boolean writeMetaExist=head.exists();
			this.headRaf=new RandomAccessFile(head, "rw");
			this.headChannel=this.headRaf.getChannel();
			this.headMappedByteBuffer=this.headChannel.map(MapMode.READ_WRITE, 0, HEAD_SIZE);
			if(writeMetaExist){// 已存在头文件
				long pos=this.headMappedByteBuffer.getLong(0);
				// 总写入偏移位置
				this.writePos=new AtomicLong(pos);
				// 总写入记录数
				this.rowCount=new AtomicLong(this.headMappedByteBuffer.getLong(8));
				if(pos>0){
					this.dataChannel.position(pos);
				}
			}else{
				this.writePos=new AtomicLong(0);
				this.rowCount=new AtomicLong(0);
			}
			
			// 缓存1K
			this.writeBuffer=ByteBuffer.allocate(1024);
			ok=true;
			if(trace){
				SinotnLogger.DEBUG.trace("init LogRegion path={},writePos={}, rowCount={}",dtfile.getCanonicalPath(),this.writePos.get(),this.rowCount.get());
			}
		}catch(Throwable e){
			SinotnLogger.DEBUG.error("初始化本地日志数据存储失败", e);
		}finally{
			if(!ok){
				this.closeResource();
			}
		}
	}
	
	private int innerWrite(ByteBuffer buffer) throws IOException{
		int byteSize=this.dataChannel.write(buffer);
		this.writePos.addAndGet(byteSize);
		return byteSize;
	}
	
	private int writeLongText(String text) throws IOException{
		byte[] data=text.getBytes(charset);
		this.writeBuffer.clear();
		int len=data.length;
		int transferSize;
		this.writeBuffer.putInt(len);// 字符长度
		int offset=0;
		while(len>0){
			transferSize=this.writeBuffer.remaining();
			if(transferSize>len){
				// writeBuffer可写空间大于数据长度
				transferSize=len;
			}
			this.writeBuffer.put(data, offset, transferSize);
			offset+=transferSize;
			len=len-transferSize;
			this.writeBuffer.flip();
			this.innerWrite(this.writeBuffer);
			this.writeBuffer.clear();
		}
		return data.length+4;
	}

	public void write(String examCertId, long roundBeginTimestamp, String eventTimeText, String eventText) {
		if(this.dataChannel==null){
			ExamLogger.RECEIVER_DEBUG.info("RtLog|beginTime={},examCertId={},eventTime={},eventText={}",roundBeginTimestamp,examCertId,eventTimeText,eventText);
		}else{
			this.lock.lock();
			// 标记当前写位置
			long markPos=this.writePos.get();
			long markRow=this.rowCount.get();
			try{
				// 准考证号
				byte[] data=examCertId.getBytes(charset);
				this.writeBuffer.clear();
				int len=data.length;
				this.writeBuffer.putInt(len);
				this.writeBuffer.put(data);
				this.writeBuffer.flip();
				int byteSize=this.innerWrite(this.writeBuffer);
				if(trace){
					SinotnLogger.DEBUG.trace("ExamCertId {} write {} byte size", examCertId, byteSize);
				}
				
				// 场次开始时间
				this.writeBuffer.clear();
				this.writeBuffer.putLong(roundBeginTimestamp);
				this.writeBuffer.flip();
				byteSize=this.innerWrite(this.writeBuffer);
				if(trace){
					SinotnLogger.DEBUG.trace("RoundBeginTime {} write {} byte size", roundBeginTimestamp, byteSize);
				}
				// 事件时间字符串
				if(eventTimeText==null){
					this.writeBuffer.clear();
					this.writeBuffer.putInt(0);
					this.writeBuffer.flip();
					byteSize=this.innerWrite(this.writeBuffer);
					if(trace){
						SinotnLogger.DEBUG.trace("EventTime is emtpy write {} byte size", byteSize);
					}
				}else{
					byteSize=this.writeLongText(eventTimeText);
					if(trace){
						SinotnLogger.DEBUG.trace("EventTime {} write {} byte size", eventTimeText, byteSize);
					}
				}
				
				// 事件内容
				if(eventText==null){
					this.writeBuffer.clear();
					this.writeBuffer.putInt(0);
					this.writeBuffer.flip();
					byteSize=this.innerWrite(this.writeBuffer);
					if(trace){
						SinotnLogger.DEBUG.trace("EventText is emtpy write {} byte size", byteSize);
					}
				}else{
					byteSize=this.writeLongText(eventText);
					if(trace){
						if(eventText.length()>50){
							eventText=eventText.substring(0, 50)+"..."+eventText.length()+"字";
						}
						SinotnLogger.DEBUG.trace("EventText {} write {} byte size", eventText, byteSize);
					}
				}
				
				// 数据文件写盘
				this.dataChannel.force(false);
				// 队列头文件，内存映射共享
				// 总写偏移位置
				this.headMappedByteBuffer.putLong(0, this.writePos.get());
				// 总记录数
				this.headMappedByteBuffer.putLong(8,this.rowCount.incrementAndGet());
				if(trace){
					SinotnLogger.DEBUG.trace("LogRegion{} writePos={} rowCount={}", this.id, this.writePos.get(), this.rowCount.get());
				}
			}catch(Throwable e){
				ExamLogger.RECEIVER_DEBUG.info("RtLog|beginTime={},examCertId={},eventTime={},eventText={}",roundBeginTimestamp,examCertId,eventTimeText,eventText);
				try{
					this.writePos.set(markPos);
					// 总写偏移位置
					this.headMappedByteBuffer.putLong(0, markPos);
					// 总记录数
					this.headMappedByteBuffer.putLong(8, markRow);
				}catch(Throwable e1){
					SinotnLogger.DEBUG.error("Rollback RtLog pos error", e);
				}
			}finally{
				this.lock.unlock();
			}
		}
	}
	
	private void consumeText(InputStream is) throws IOException{
		int len=StreamUtil.readInt(is);
		if(len>0){
			is.skip(len);
			if(trace){
				SinotnLogger.DEBUG.trace("skip {} byte", len);
			}
		}
	}

	public List<RuntimeLog> getLogs(String examCertId, long roundBeginTimestamp) {
		if(this.dataChannel==null) return null;
		long pos=this.writePos.get();
		if(trace){
			SinotnLogger.DEBUG.trace("getLogs last pos={}", pos);
		}
		if(pos==0) return null;
		File dtfile=new File(this.folder, DATA_FILE);
		if(!dtfile.exists()) return null;
		ArrayList<RuntimeLog> list=new ArrayList<RuntimeLog>(100);
		FileInputStream fis=null;
		try{
			fis=new FileInputStream(dtfile);
			String text;
			long timestamp;
			String eventTime;
			String eventText;
			RuntimeLog runtimeLog;
			int remainSize;
			int readSize;
			while(pos>0){
				// 准考证号
				remainSize=fis.available();
				text=StreamUtil.readText(fis);
				// 场次开始时间
				timestamp=StreamUtil.readLong(fis);
				if(timestamp==roundBeginTimestamp && text.equals(examCertId)){
					// 日志行为时间
					eventTime=StreamUtil.readText(fis);
					// 日志行为内容
					eventText=StreamUtil.readText(fis);
					runtimeLog=new RuntimeLog();
					runtimeLog.setTime(eventTime);
					runtimeLog.setText(eventText);
					list.add(runtimeLog);
				}else{
					// 日志行为时间
					this.consumeText(fis);
					// 日志行为内容
					this.consumeText(fis);
				}
				readSize=remainSize-fis.available();
				pos-=readSize;
				if(trace){
					SinotnLogger.DEBUG.trace("available {} byte, read {} byte, leave {} byte for read", remainSize, readSize, pos);
				}
			}
			if(trace){
				SinotnLogger.DEBUG.trace("getLogs examCertId={}, beginTime={} match {} row", examCertId, roundBeginTimestamp, list.size());
			}
		}catch(Throwable e){
			SinotnLogger.DEBUG.error("查询答题行为日志失败[examCertId="+examCertId+";beginTime="+roundBeginTimestamp+"]", e);
		}finally{
			if(fis!=null){
				try{
					fis.close();
				}catch(Throwable e){}
			}
		}
		return list;
	}

	public int merge(WritableByteChannel channel) {
		if(this.dataChannel==null) return 0;
		long pos=this.writePos.get();
		if(pos==0) return 0;
		File dtfile=new File(this.folder, DATA_FILE);
		if(!dtfile.exists()) return 0;
		FileInputStream fis=null;
		try{
			fis=new FileInputStream(dtfile);
			int availableSize=fis.available();
			long transferSize=fis.getChannel().transferTo(0, pos, channel);
			if(trace){
				SinotnLogger.DEBUG.trace("available {} byte, transfer {} byte", availableSize, transferSize);
			}
		}catch(Throwable e){
			SinotnLogger.DEBUG.error("transfer region data error", e);
		}finally{
			if(fis!=null){
				try{
					fis.close();
				}catch(Throwable e){}
			}
		}
		return this.rowCount.intValue();
	}

	public void clear() {
		if(this.dataChannel!=null){
			this.closeResource();
			File[] files=this.folder.listFiles();
			if(files!=null && files.length>0){
				int len=files.length;
				for(int i=0;i<len;i++){
					files[i].delete();
				}
			}
			this.folder.delete();
		}
	}
}
